from messageloop import messageloop
import socket


class IOExcept(Exception):
    def __init__(self, message):
        self.message = message
        self.loop = messageloop.MessageLoop()


class HttpURLConnection:
    # request method
    methods = ["GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"]

    def __init__(self, url, port=80):
        # response header headers
        self.requestHeaders = {}
        self.responseHeaders = {}
        self.requestBody = bytearray()
        self.responseBody = bytearray()
        self.connected = False
        self.receivedResponseBody = False
        self.port = port
        self.socket = None
        # Default connect timeout is 10 sec
        self.connectTimeout = 10 * 1000
        # Default reqeust method is GET
        self.method = "GET"
        # Http response
        self.responseHeaderParsed = False
        self.__parseUrl(url)

    def __parseUrl(self, url):
        if not url:
            raise IOExcept("url is empty")
        # parse "http://www.baidu.com:8080/test/api?id=1222"
        protocolPrefix = "http://"
        if not url.startswith(protocolPrefix):
            raise IOExcept("{} is not http protocol".format(url))
        # rest = "www.baidu.com:8080/test/api?id=1222"
        rest = url[len(protocolPrefix):]
        if not rest:
            raise IOExcept("host is empty")
        e = IOExcept("Illegal url {}".format(url))
        items = rest.split("/")
        # parse path by split "/"
        pos = rest.find("/")
        if pos == -1:
            host = rest
            self.path = "/"
        else:
            host = rest[0:pos]
            self.path = rest[pos:]
        if not host or not self.path:
            raise e

        # parse host and port by split ":"
        items = host.split(":") # www.baidu.com:8080
        if len(items) == 2:
            self.host = items[0]
            port = items[1]
            if not port:
                raise e
        elif len(items) == 1:
            self.host = items[0]
            port = "80"
        else:
            raise e
        if not self.host or not port:
            raise e
        try:
            self.port = int(port)
        except ValueError:
            raise e
        items = host.split(".")
        if len(items) < 2:
            raise e
        print("host: {}, port: {}, path: {}".format(self.host, self.port, self.path))
        self.setRequestProperty("Host", host)

    def __parseHeader(self):
        # read http response header into lines
        lines = []
        line = bytearray()
        while True:
            receivedData = self.socket.recv(1)
            if not receivedData or len(receivedData) != 1:
                raise IOExcept("Failed to parse response head")
            if receivedData == b'\n':
                # last line of http header
                if line == b'\r' or not line:
                    break
                else:
                    if line.endswith(b'\r'):
                        line = line[:-1]
                    lines.append(line)
                    line = bytearray()
            else:
                line.extend(receivedData)

        if not lines or len(lines) < 1:
            raise IOExcept("Failed to parse response header")
        # parse "HTTP/1.1 200 OK"
        statusLine = lines[0].decode('utf-8')
        if not statusLine.startswith("HTTP/1."):
            raise IOExcept("Unexpected status line: " + statusLine)
        self.httpStatusCode = int(statusLine[9:12])
        self.httpResponseMessage = statusLine[13:]
        print("Response status code = " + str(self.httpStatusCode) + ", message = " + self.httpResponseMessage)
        # parse headers
        for line in lines[1:]:
            if not line:
                continue
            line = line.decode('utf-8')
            pos = line.find(':')
            if pos < 1:
                raise IOExcept("Unexpected header line: " + line)
            key = line[0:pos].strip(' ')
            value = line[pos+1:].strip(' ')
            self.responseHeaders[key] = value
        print("Response Headers: " + str(self.responseHeaders))
        self.responseHeaderParsed = True

    def __getResponse(self, block = True):
        if self.responseHeaderParsed:
            return
        ip = socket.gethostbyname(self.host)
        if not ip:
            raise IOExcept("Failed to lookup ip for host " + self.host)
        self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.socket.connect((self.host, self.port))
        self.connected = True
        self.__sendFirstLIne()
        self.__sendHeaders()
        self.__sendHeadFinishLine()
        self.__sendRequestBody()
        self.__parseHeader()

    def __sendData(self, data):
        if not data or len(data) == 0:
            return
        self.socket.send(data)

    def __sendFirstLIne(self):
        data = bytearray("{} {} HTTP/1.1\r\n".format(self.method, self.path).encode())
        self.__sendData(data)

    def __sendHeaders(self):
        bodyLen = len(self.requestBody)
        if bodyLen > 0:
            self.__setRequestProperty("Content-Length", str(bodyLen))
        for kv in self.requestHeaders.items():
            data = bytearray("{}: {}\r\n".format(kv[0], kv[1]).encode())
            self.__sendData(data)

    def __sendHeadFinishLine(self):
        self.__sendData(bytearray('\r\n'.encode()))

    def __sendRequestBody(self):
        if len(self.requestBody) > 0:
            self.__sendData(self.requestBody)

    def __getResponseBody(self):
        readLen = 1024
        while True:
            receivedData = self.socket.recv(readLen)
            if not receivedData or len(receivedData) < 1:
                break
            self.responseBody.extend(receivedData)
        self.receivedResponseBody = True

    def getResponse(self):
        self.__getResponse(True)

    def getResponseCode(self):
        self.__getResponse()
        if not self.responseHeaderParsed:
            raise IOExcept("Failed to parse response")
        return self.httpStatusCode

    def getResponseMessage(self):
        self.__getResponse()
        if not self.responseHeaderParsed:
            raise IOExcept("Failed to parse response")
        return self.httpResponseMessage

    def setConnectTimeout(self, timeout):
        if self.connected:
            raise BaseException("Can't reset connet timeout: already connected")
        if timeout < 0:
            raise BaseException("timeout can not be negative")
        self.connectTimeout = timeout

    def getConnectTimeout(self):
        return self.connectTimeout

    def getRequestMethod(self):
        return self.method

    def setRequestMethod(self, method):
        if self.connected:
            raise BaseException("Can't reset method: already connected")
        if method != "GET" and method != "POST":
            raise Exception("method must be GET or POST")
        self.method = method

    def getHeaderFields(self):
        self.__getResponse()
        return self.responseHeaders

    def getHeaderField(self, key):
        self.__getResponse()
        if not isinstance(key, str):
            raise Exception('key is illegal')
        return self.responseHeaders[key]

    def getRequestProperty(self, key):
        if not isinstance(key, str):
            raise Exception('key is illegal')
        return self.requestHeaders[key]

    def getRequestProperties(self):
        return self.requestHeaders

    def setRequestProperty(self, key, value):
        if self.connected:
            raise BaseException("Can't reset request property: already connected")
        self.__setRequestProperty(key, value)

    def __setRequestProperty(self, key, value):
        if not key:
            raise Exception("Header key cannot be empty")
        # value could be empty, just ignore it
        if not value:
            return
        if not isinstance(key, str) or not isinstance(value, str):
            raise Exception("key or is not a string")
        self.requestHeaders[key] = value

    def setRequestData(self, data):
        if self.connected:
            raise BaseException("Can't reset request data: already connected")
        if not data or len(data) == 0:
            return
        self.requestBody.extend(data)

    def getResponseData(self):
        self.__getResponse()
        self.__getResponseBody()
        if not self.receivedResponseBody:
            raise IOExcept("Failed to get response body")
        return self.responseBody


    def close(self):
        if self.socket:
            self.socket.close()

    def setTestPath(self, path):
        self.path = path


if __name__ == "__main__":
    # host = "www.baidu.com"
    # ip = socket.gethostbyname(host)
    # print("{} ip: {}".format(host, ip))
    # url = "http://www.baidu.com"
    # if url.startswith("http://"):
    #     print("{} is http protocol".format(url))
    # pos = host.find("/")
    # print(pos)
    url = "http://www.baidu.com"
    conn = HttpURLConnection(url)
    conn.getResponse()

    # sk = socket.socket()
    # sk.connect(("www.baidu.com", 80))

